﻿function DateTimeFormatter() {
    var self = this;

    function formatInt(val, length) {
        var res = val.toString();
        while (res.length < length) {
            res = '0' + res;
        }
        return res;
    }

    this.time = function (date) {
        var res = '';
        res += formatInt(date.getHours(), 2);
        res += ':';
        res += formatInt(date.getMinutes(), 2);
        res += ':';
        res += formatInt(date.getSeconds(), 2);
        return res;
    };

    this.date = function (date) {
        var res = '';
        res += formatInt(date.getFullYear(), 4);
        res += '-';
        res += formatInt(date.getMonth() + 1, 2);
        res += '-';
        res += formatInt(date.getDate(), 2);
        return res;
    };

    this.dateTime = function (date) {
        return self.dateFormat(date, 'MM/dd/yyyy h:mm:ss tt');
    };

    this.shortDateTime = function (date, range) {
        if (range <= 24 * 60 * 60) {
            return this.time(date);
        }
        return this.date(date);
    };

    this.dateFormat = function (date, format) {
        var year = date.getFullYear();
        var month = date.getMonth();
        var day = date.getDate();
        var hours = date.getHours();
        var minutes = date.getMinutes();
        var seconds = date.getSeconds();
        var millis = date.getMilliseconds();
        var res = '';
        for (var i = 0; i < format.length;) {
            if (format.indexOf('MMMM', i) == i) {
                i += 4;
                res += monthNames[month];
            } else if (format.indexOf('MMM', i) == i) {
                i += 3;
                res += monthsShortNames[month];
            } else if (format.indexOf('MM', i) == i) {
                i += 2;
                res += formatInt(month + 1, 2);
            } else if (format.indexOf('yyyy', i) == i) {
                i += 4;
                res += formatInt(year, 4);
            } else if (format.indexOf('dd', i) == i) {
                i += 2;
                res += formatInt(day, 2);
            } else if (format.indexOf('d', i) == i) {
                i += 1;
                res += day.toString();
            } else if (format.indexOf('HH', i) == i) {
                i += 2;
                res += formatInt(hours, 2);
            } else if (format.indexOf('H', i) == i) {
                i += 1;
                res += hours.toString();
            } else if (format.indexOf('h', i) == i) {
                i += 1;
                if (hours == 0 || hours == 12) {
                    res += '12';
                } else {
                    res += (hours % 12).toString();
                }
            } else if (format.indexOf('mm', i) == i) {
                i += 2;
                res += formatInt(minutes, 2);
            } else if (format.indexOf('ss', i) == i) {
                i += 2;
                res += formatInt(seconds, 2);
            } else if (format.indexOf('ffffff', i) == i) {
                i += 6;
                res += formatInt(millis, 3) + '000';
            } else if (format.indexOf('tt', i) == i) {
                i += 2;
                if (hours >= 12) res += 'PM';
                else res += 'AM';
            } else {
                res += format.charAt(i);
                i++;
            }
        }
        return res;
    };

    function createParseContext(format) {
        return {
            year: 0,
            hours: 0,
            month: 0,
            day: 0,
            minutes: 0,
            seconds: 0,
            fraction: 0,
            format: format,
            sourcePos: 0,
            formatPos: 0,
            isFormatEof: function () {
                return this.formatPos >= this.format.length;
            },
            formatStartsWith: function (str) {
                return this.format.indexOf(str, this.formatPos) == this.formatPos;
            },
            formatCheckAndSkip: function (str) {
                if (this.formatStartsWith(str)) {
                    this.formatPos += str.length;
                    return true;
                }
                return false;
            },
            readFormatChar: function () {
                var ch = this.format.charAt(this.formatPos);
                this.formatPos++;
                return ch;
            },
            status: 'ok',
            failed: function () {
                this.status = 'failed';
            }
        };
    }

    function parseDate(str, context) {

        if (str == null) return null;

        function takeDigits(minCount, maxCount) {
            var val = 0;
            var digitsCount = 0;
            while (context.sourcePos < str.length && digitsCount < maxCount) {
                var chr = str.charAt(context.sourcePos);
                if (chr < '0' || chr > '9') {
                    if (digitsCount >= minCount) return val;
                    return -1;
                }
                val = val * 10 + (chr - '0');
                digitsCount++;
                context.sourcePos++;
            }
            return val;
        }

        while (!context.isFormatEof()) {
            if (context.formatCheckAndSkip('MMM')) {
                var monthName = str.substr(context.sourcePos, 3);
                var month = getMonthByShortName(monthName);
                if (month < 0) return context.failed();
                context.sourcePos += 3;
                context.month = month;
            } else if (context.formatCheckAndSkip('MM')) {
                var monthDirect = takeDigits(2, 2);
                if (monthDirect < 0) return context.failed();
                context.month = monthDirect - 1;
            } else if (context.formatCheckAndSkip('yyyy')) {
                var year = takeDigits(4, 4);
                if (year < 0) return context.failed();
                context.year = year;
            } else if (context.formatCheckAndSkip('dd')) {
                var dday = takeDigits(2, 2);
                if (dday < 0) return context.failed();
                context.day = dday;
            } else if (context.formatCheckAndSkip('d')) {
                var day = takeDigits(1, 2);
                if (day < 0) return context.failed();
                context.day = day;
            } else if (context.formatCheckAndSkip('HH')) {
                var hours = takeDigits(2, 2);
                if (hours < 0) return context.failed();
                context.hours = hours;
            } else if (context.formatCheckAndSkip('H')) {
                var hours = takeDigits(1, 2);
                if (hours < 0) return context.failed();
                context.hours = hours;
            } else if (context.formatCheckAndSkip('mm')) {
                var minutes = takeDigits(2, 2);
                if (minutes < 0) return context.failed();
                context.minutes = minutes;
            } else if (context.formatCheckAndSkip('ss')) {
                var seconds = takeDigits(2, 2);
                if (seconds < 0) return context.failed();
                context.seconds = seconds;
            } else if (context.formatCheckAndSkip('ffffff')) {
                var fraction = takeDigits(1, 6);
                if (fraction < 0) return context.failed();
                context.fraction = fraction;
            } else {
                var formatCh = context.readFormatChar();
                if (context.sourcePos >= str.length || formatCh != str[context.sourcePos]) {
                    return context.failed();
                }
                context.sourcePos++;
            }
        }
        return context;
    }

    this.parseDateTime = function (str, format) {
        var ctx = createParseContext(format);
        parseDate(str, ctx);
        if (ctx.status == 'ok') {
            try {
                return new Date(ctx.year, ctx.month, ctx.day, ctx.hours, ctx.minutes, ctx.seconds, Math.round(1000 * ctx.fraction));
            } catch (e) {
                return null;
            }
        }
        return null;
    };

    this.parseTime = function (str, format) {
        var ctx = createParseContext(format);
        parseDate(str, ctx);
        if (ctx.status == 'ok') {
            return ((ctx.hours * 60 + ctx.minutes) * 60 + ctx.seconds) * 1000 +  Math.round(1000 * ctx.fraction);
        }
        return -1;
    };

    function getMonthByShortName(str) {
        str = str.toLocaleLowerCase();
        for (var i = 0; i < monthsShortNames.length; i++) {
            var monthName = monthsShortNames[i].toLocaleLowerCase();
            if (monthName == str) return i;
        }
        return -1;
    }
    var monthsShortNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
    var monthNames = ['January', 'Feburay', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
}